// 第 6 步：补充结果返回与资源清理

#pragma once

// step 1:
// 先写出头文件常用的 #pragma once、依赖的系统头文件、命名空间声明。这一步暂时只保留最基础的框架
#include "logging.h"
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <string>
#include <sstream>
#include <fcntl.h>

namespace Common {

// step 2:
// 这个结构体封装创建 socket 所需的参数，同时提供 toString() 便于记录日志。
struct SocketCfg {
  std::string ip_;
  std::string iface_;
  int port_ = -1;
  bool is_udp_ = false;
  bool is_listening_ = false;
  bool needs_so_timestamp_ = false;

  auto toString() const {
    std::stringstream ss;
    ss << "SocketCfg[ip:" << ip_
       << " iface:" << iface_
       << " port:" << port_
       << " is_udp:" << is_udp_
       << " is_listening:" << is_listening_
       << " needs_SO_timestamp:" << needs_so_timestamp_
       << "]";
    return ss.str();
  }
};

constexpr int MaxTCPServerBacklog = 1024;

inline auto getIfaceIP(const std::string &iface) -> std::string {
  char buf[NI_MAXHOST] = {'\0'};
  ifaddrs *ifaddr = nullptr;

  if (getifaddrs(&ifaddr) != -1) {
    for (ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
      if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET && iface == ifa->ifa_name) {
        getnameinfo(ifa->ifa_addr, sizeof(sockaddr_in), buf, sizeof(buf), NULL, 0, NI_NUMERICHOST);
        break;
      }
    }
    freeifaddrs(ifaddr);
  }

  return buf;
}

// step3:
// 加入最大监听队列 MaxTCPServerBacklog，以及 getIfaceIP / setNonBlocking / disableNagle 等基础工具函数。它们将被后面的 createSocket 使用。
inline auto setNonBlocking(int fd) -> bool {
  const auto flags = fcntl(fd, F_GETFL, 0);
  if (flags & O_NONBLOCK)
    return true;
  return (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1);
}

inline auto disableNagle(int fd) -> bool {
  int one = 1;
  return (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<void *>(&one), sizeof(one)) != -1);
}

inline auto setSOTimestamp(int fd) -> bool {
  int one = 1;
  return (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, reinterpret_cast<void *>(&one), sizeof(one)) != -1);
}

inline auto join(int fd, const std::string &ip) -> bool {
  const ip_mreq mreq{{inet_addr(ip.c_str())}, {htonl(INADDR_ANY)}};
  return (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != -1);
}

// step 4:
// 该函数将结合上面的工具函数与 Logger 完成 socket 的创建和配置。先写出函数签名、日志记录入口，以及 getaddrinfo 的准备工作。
[[nodiscard]] inline auto createSocket(Logger &logger, const SocketCfg& socket_cfg) -> int {
  std::string time_str;

  const auto ip = socket_cfg.ip_.empty() ? getIfaceIP(socket_cfg.iface_) : socket_cfg.ip_;
  logger.log("%:% %() % cfg:%\n", __FILE__, __LINE__, __FUNCTION__,
             Common::getCurrentTimeStr(&time_str), socket_cfg.toString());

  const int input_flags = (socket_cfg.is_listening_ ? AI_PASSIVE : 0) | (AI_NUMERICHOST | AI_NUMERICSERV);
  const addrinfo hints{input_flags, AF_INET, socket_cfg.is_udp_ ? SOCK_DGRAM : SOCK_STREAM,
                       socket_cfg.is_udp_ ? IPPROTO_UDP : IPPROTO_TCP, 0, 0, nullptr, nullptr};

  addrinfo *result = nullptr;
  const auto rc = getaddrinfo(ip.c_str(), std::to_string(socket_cfg.port_).c_str(), &hints, &result);
  ASSERT(!rc, "getaddrinfo() failed. error:" + std::string(gai_strerror(rc)) + "errno:" + strerror(errno));

  // step 5
  // 在 for 循环里依次尝试创建 socket，设置非阻塞、禁用 Nagle、connect/bind/listen 等操作，并按照 UDP/TCP、客户端/服务端的不同分支处理。
  int socket_fd = -1;
  int one = 1;

  for (addrinfo *rp = result; rp; rp = rp->ai_next) {
    ASSERT((socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) != -1,
          "socket() failed. errno:" + std::string(strerror(errno)));

    ASSERT(setNonBlocking(socket_fd), "setNonBlocking() failed. errno:" + std::string(strerror(errno)));

    if (!socket_cfg.is_udp_) {
      ASSERT(disableNagle(socket_fd), "disableNagle() failed. errno:" + std::string(strerror(errno)));
    }

    if (!socket_cfg.is_listening_) {
      ASSERT(connect(socket_fd, rp->ai_addr, rp->ai_addrlen) != 1,
            "connect() failed. errno:" + std::string(strerror(errno)));
    }

    if (socket_cfg.is_listening_) {
      ASSERT(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
                        reinterpret_cast<const char *>(&one), sizeof(one)) == 0,
            "setsockopt() SO_REUSEADDR failed. errno:" + std::string(strerror(errno)));
    }

    if (socket_cfg.is_listening_) {
      const sockaddr_in addr{AF_INET, htons(socket_cfg.port_), {htonl(INADDR_ANY)}, {}};
      ASSERT(bind(socket_fd,
                  socket_cfg.is_udp_ ? reinterpret_cast<const struct sockaddr *>(&addr) : rp->ai_addr,
                  sizeof(addr)) == 0,
            "bind() failed. errno:%" + std::string(strerror(errno)));
    }

    if (!socket_cfg.is_udp_ && socket_cfg.is_listening_) {
      ASSERT(listen(socket_fd, MaxTCPServerBacklog) == 0,
            "listen() failed. errno:" + std::string(strerror(errno)));
    }

    if (socket_cfg.needs_so_timestamp_) {
      ASSERT(setSOTimestamp(socket_fd), "setSOTimestamp() failed. errno:" + std::string(strerror(errno)));
    }
  }

  // step 6:
  // 遍历完成后返回成功的 socket 描述符。同时别忘了在合适的位置释放 addrinfo 资源（虽然示例代码未显式释放，但实际使用应调用 freeaddrinfo(result)；可根据项目约定补充）。
  freeaddrinfo(result);
  return socket_fd;
}

} // namespace Common